﻿namespace CloudNinja.Metering.Authentication
{
    using System;
    using System.Linq;
    using System.Net;
    using System.Net.Http;
    using System.Security.Cryptography;
    using System.ServiceModel;
    using System.ServiceModel.Channels;
    using System.Text;
    using System.Web;

    using CloudNinja.Metering.Data;

    public class HmacHelper : IHmacHelper
    {
        private const string DateHeader = "x-ms-date";

        private const string SignatureFormatString = "{0}\n{1}\n{2}\n{3}\n{4}{5}";

        private readonly IApiKeyStore keyStore;

        public HmacHelper(IApiKeyStore keyStore)
        {
            this.keyStore = keyStore;
        }

        public string CreateAuthenticationToken(string userId, WebRequest request)
        {
            var textBytes = Encoding.UTF8.GetBytes(BuildRequestRepresentation(userId, new WebRequestAdapter(request)));

            using (var hmac = new HMACSHA256(this.keyStore.GetServiceKey(userId)))
            {
                var computedHash = hmac.ComputeHash(textBytes);
                return Convert.ToBase64String(computedHash);
            }
        }

        public string ValidateAuthenticationToken(HttpRequestMessage httpRequest)
        {
            var request = new HttpRequestMessageAdapter(httpRequest);
            var authorizationHeaderValue = request.AuthorizationHeaderValue;

            // check that a value is there
            if (string.IsNullOrEmpty(authorizationHeaderValue))
            {
                return null;
            }

            var userId = Encoding.UTF8.GetString(Convert.FromBase64String(request.UserHeader));

            if (ValidateHash(BuildRequestRepresentation(userId, request), authorizationHeaderValue, this.keyStore.GetServiceKey(userId)))
            {
                return userId;
            }

            return null;
        }

        private static string BuildRequestRepresentation(string userId, IRequestAdapter request)
        {
            if (request == null)
            {
                return String.Empty;
            }

            if (!IsRequestValid(request))
            {
                return string.Empty;
            }

            var date = request.DateHeader;
            if (string.IsNullOrEmpty(date))
            {
                return string.Empty;
            }


            // We can also put in the hash for the content, let's use empty for now.
            var contentMd5 = string.Empty;
            var method = request.Method;

            var contentType = request.ContentType;

            return string.Format(
                SignatureFormatString,
                method,
                contentMd5,
                contentType,
                date,
                GetCanonicalizedHeaders(request),
                GetCanonicalizedResource(userId, request));
        }

        private static string GetCanonicalizedHeaders(IRequestAdapter request)
        {
            var headerNames = request.HeadersKeys
                              .Where(header => header.ToLowerInvariant().StartsWith("x-ms-", StringComparison.Ordinal))
                              .ToList();

            headerNames.Sort();

            var sb = new StringBuilder();
            foreach (var headerName in headerNames)
            {
                sb.AppendFormat("{0}:{1}\n", headerName, request.GetHeaderValue(headerName));
            }

            return sb.ToString();
        }

        private static string GetCanonicalizedResource(string userId, IRequestAdapter request)
        {
            var sb = new StringBuilder();
            sb.AppendFormat("/{0}/", userId);
            sb.Append(request.RequestLocalPath);
            sb.Append("\n");

            var nameValueCollection = HttpUtility.ParseQueryString(request.Query);
            foreach (var key in nameValueCollection.Keys)
            {
                sb.AppendFormat("{0}:{1}\n", key.ToString().ToLowerInvariant(), nameValueCollection[key.ToString()]);
            }

            return sb.ToString();
        }

        private static bool IsRequestValid(IRequestAdapter request)
        {
            // TODO: Check headers here.
            return true;
        }

        private static bool ValidateHash(string text, string hash, byte[] key)
        {
            if (string.IsNullOrEmpty(text) || string.IsNullOrEmpty(hash))
            {
                return false;
            }

            if (key == null || key.Length == 0)
            {
                return false;
            }

            var textBytes = Encoding.UTF8.GetBytes(text);
            using (var hmac = new HMACSHA256(key))
            {
                var computedHash = hmac.ComputeHash(textBytes);
                var computedHashString = Convert.ToBase64String(computedHash);
                return hash.Equals(computedHashString);
            }
        }
    }
}
